#include <CL/opencl.h>
#include <math.h>
#include <string.h>

#include <ostream>
#include <vector>

namespace PGL {
	class pglVector;
	class pglMatrix;
	class pglNormalVertex;

	// Light class
	class pglLight
	{
	public:
		cl_float4 ambient;
		cl_float4 diffuse;
		cl_float4 specular;
		cl_float4 position;

		inline bool operator == (const pglLight &other) const
		{
			return memcmp(this,&other,sizeof(pglLight)) == 0;
		}
	};

	// Vector class
	class pglVector
	{
	public:
		// Constructors
		pglVector();
		pglVector(const pglVector &other);
		pglVector(cl_float x, cl_float y, cl_float z);
		pglVector(cl_float x, cl_float y, cl_float z, cl_float w);

		// Get values by xyzw
		const cl_float x() const {return vec.s[0];}
		const cl_float y() const {return vec.s[1];}
		const cl_float z() const {return vec.s[2];}
		const cl_float w() const {return vec.s[3];}

		// Get values by rgba
		const cl_float r() const {return vec.s[0];}
		const cl_float g() const {return vec.s[1];}
		const cl_float b() const {return vec.s[2];}
		const cl_float a() const {return vec.s[3];}

		// Operators (non-const)
		cl_float& operator[](unsigned int i);
		pglVector& operator=(const pglVector &other);
		pglVector& operator+=(const pglVector &other);

		// Operators (const)
		const cl_float operator[](unsigned int i) const;
		pglVector operator+(const pglVector &other) const;
		pglVector operator-(const pglVector &other) const;
		pglVector operator-(void) const;
		pglVector operator*(const cl_float &scalar) const;
		pglVector operator/(const cl_float &scalar) const;
		cl_float operator*(const pglVector &other) const;
		pglVector operator*(const pglMatrix &mat) const;
		pglVector operator^(const pglVector &other) const;


		// Vector functions
		cl_float magnitude() const;
		pglVector normalize() const;

	private:
		cl_float4 vec;
	};

	// Debug operators
	std::ostream& operator<<(std::ostream &out, pglVector &pglVec);

	class pglNormalVertex
	{
	public:
		pglNormalVertex();
		pglNormalVertex(pglVector norm, pglVector pos);

		PGL::pglVector getPosition() const { return position; }
		void setPosition(PGL::pglVector val) { position = val; }

		PGL::pglVector getNormal() const { return normal; }
		void setNormal(PGL::pglVector val) { normal = val; }
	private:
		pglVector normal;
		pglVector position;
	};

	// Identity matrix
	const cl_float4 pglIdentityMatrix[] = 
	{
		{1,0,0,0},
		{0,1,0,0},
		{0,0,1,0},
		{0,0,0,1}
	};

	// Structure for matrices
	class pglMatrix
	{
	public:
		pglMatrix();
		pglMatrix(const pglVector row0, const pglVector row1, const pglVector row2, const pglVector row3);

		pglMatrix& operator=(const pglMatrix &other);
		pglMatrix operator*(const pglMatrix &other) const;
		pglVector& operator[](unsigned int i);
		const pglVector operator[](unsigned int i) const;
	private:
		pglVector r[4];
	};

	// Matrices
	void matrixLoadIdentity(pglMatrix* out);
	void matrixMatrixMultiply(pglMatrix* out, pglMatrix left, pglMatrix right);
	void matrixLoadTranslationToXYZ(pglMatrix* out, pglVector trans);
	void matrixLoadRotationAboutX(pglMatrix* out, cl_float rotation);
	void matrixLoadRotationAboutY(pglMatrix* out, cl_float rotation);
	void matrixLoadRotationAboutZ(pglMatrix* out, cl_float rotation);
	void matrixLoadRotationAboutAxis(pglMatrix* out, cl_float rotation, pglVector axis);
	void matrixLoadLookAt(pglMatrix* out, pglVector eye, pglVector at, pglVector up);
	void matrixLoadOrthographicProjection(pglMatrix* out, cl_float right, cl_float left, cl_float top, cl_float bottom, cl_float near, cl_float far);
	void matrixLoadPerspectiveProjection(pglMatrix* out, cl_float fovy, cl_float aspect, cl_float zNear, cl_float zFar);
	void matrixLoadViewportTransform(pglMatrix* out, cl_float x, cl_float y, cl_float width, cl_float height, cl_float zNear, cl_float zFar);

	// Structure for mem objects including pointer
	struct pglMemObject
	{
		cl_mem memObject;
		void* hostPtr;
		size_t memSize;
	};

	// Enum for errors
	typedef enum PGLErrorEnum
	{
		PGL_SUCCESS = 0,
		PGL_OPENCL_ERROR,
		PGL_FILE_NOT_FOUND,
		PGL_NO_SUCH_LIGHT,
	};
}